package com.onpositive.realmview.model.filtering;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import com.onpositive.semantic.model.api.property.Category;
import com.onpositive.semantic.model.api.property.DefaultPropertyMetadata;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.property.adapters.IRealmProvider;
import com.onpositive.semantic.model.api.property.adapters.ITextLabelProvider;
import com.onpositive.semantic.model.core.IKnownsImageObject;
import com.onpositive.semantic.model.core.PrettyFormat;
import com.onpositive.semantic.model.realm.IFilter;
import com.onpositive.semantic.model.realm.IRealm;
import com.onpositive.semantic.model.realm.ISimpleChangeListener;

public class BasicValueFilter implements IUIFilter,IKnownsImageObject {

	
	
	public static final int MODE_PATTERN = 1;

	public static final int MODE_RANGE = 2;

	public static final int MODE_VALUES = 0;
	
	
	
    public static final String RANGE_ENCODED = "ONPOSITIVE_MAGIC_RANGE_ENCODED:";

	private ICustomFilter adapter;

	protected boolean customFilter;
	
	/**
	 * Direct-inverse filter mode flag
	 */
	protected boolean inverse = false;

	Comparable leftBound;

	Comparable rightBound;

	protected HashSet<ISimpleChangeListener<? extends IFilter>> listeners = new HashSet<ISimpleChangeListener<? extends IFilter>>();
	int mode;
	protected final IProperty pr;

	protected LinkedHashSet<Object> values = new LinkedHashSet<Object>();

	public BasicValueFilter(IProperty pr) {
		super();
		this.pr = pr;
		adapter = pr.getPropertyMetaData().getAdapter(ICustomFilter.class);		
	}

	public boolean accept(Object element) {
		if (inverse)
			return !innerAccept(element);
		return innerAccept(element);
	}

	protected boolean innerAccept(Object element)
	{
		if (mode==MODE_RANGE){
			if (leftBound==null&&rightBound==null){
				return true;
			}
			Set<Object> values2 = pr.getValues(element);
			for (Object o : values2) {
				if (leftBound!=null){
					if (leftBound.compareTo(o)>0){
						continue;
					}
				}
				if (rightBound!=null){
					if (rightBound.compareTo(o)<0){
						continue;
					}
				}
				return true;				
			}
			
			return false;
		}
		if (values.isEmpty()) {
			return true;
		}
		Set<Object> values2=null;
		if (adapter!=null){
			values2=adapter.getFilterValue(element);
		}
		else{
			values2 = pr.getValues(element);
		}
		for (Object o : values) {
			if (values2.contains(o)) {
				return true;
			}
		}
		return false;
	}

	public void addFilterListener(
			ISimpleChangeListener<? extends IFilter> listener) {
		listeners.add(listener);
	}

	@SuppressWarnings("unchecked")
	public void addValue(Object data) {
		if (data == null) {
			values.clear();
		} else {
			values.add(data);
		}
		resetMatcher();
		for (ISimpleChangeListener l : listeners) {
			l.changed((IFilter) this, data);
		}
	}

	@SuppressWarnings("unchecked")
	public void clear() {
		if (!values.isEmpty()) {
			values.clear();
			for (ISimpleChangeListener l : listeners) {
				l.changed((IFilter) this, null);
			}
		}
	}

	
	public IUIFilter clone() {
		BasicValueFilter basicValueFilter = new BasicValueFilter(pr);
		basicValueFilter.values = new LinkedHashSet<Object>(this.values);
		basicValueFilter.resetMatcher();
		return basicValueFilter;
	}

	private String emptyDesc() {
		return getName() + "\n" + "Any";
	}

	
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BasicValueFilter other = (BasicValueFilter) obj;
		if (pr == null) {
			if (other.pr != null)
				return false;
		} else if (!pr.equals(other.pr))
			return false;
		if (!this.values.equals(other.values)) {
			return false;
		}

		return true;
	}

	public Set<Object> getCurrentSelection() {
		if (values.isEmpty()) {
			return Collections.singleton(null);
		}
		return new HashSet<Object>(values);
	}
	public String getDescription() {
		if (inverse)
			return "Not " + innerGetDesription();
		return innerGetDesription();
	}

	protected String innerGetDesription()
	{
		ITextLabelProvider adapter2 = pr.getPropertyMetaData().getAdapter(ITextLabelProvider.class);
		StringBuilder bld = new StringBuilder();
		if (mode==MODE_RANGE){
			if (leftBound==null&&rightBound==null){
				return emptyDesc();
			}
			bld.append("from ");
			if (leftBound!=null){
			bld.append(adapter2!=null?adapter2.getText(leftBound):PrettyFormat.format(leftBound,false));
			}
			else{
				bld.append('*');
			}
			bld.append(" to ");
			if (rightBound!=null){
			bld.append(adapter2!=null?adapter2.getText(rightBound):PrettyFormat.format(rightBound,false));
			}
			else{
				bld.append('*');
			}
			return getName() + "\n"+bld.toString();
		}
		if (values.isEmpty()) {
			return emptyDesc();
		}
		

		for (Object o : values) {
			bld.append(adapter2!=null?adapter2.getText(o):o);
			bld.append(" or ");
		}
		if (bld.length() > 0) {
			bld.delete(bld.length() - 4, bld.length() - 1);
		}
		String string = bld.toString().trim();

		if (string.length() > 40) {
			int indexOf = string.indexOf(' ');
			int indexOf1 = string.lastIndexOf(' ');
			if (indexOf!=indexOf1){
			string = string.substring(0, indexOf) + " ... "
					+ string.substring(indexOf1 + 1);
			}
		}
		return getName() + "\n" + string;
	}
	public Set<Category> getGroup() {
		return ((DefaultPropertyMetadata)pr.getPropertyMetaData()).getGroup();
	}



	public String getId() {
		return pr.getId();
	}

	public String getImageID() {
		DefaultPropertyMetadata meta=((DefaultPropertyMetadata)pr.getPropertyMetaData());
		return (String) meta.get(DefaultPropertyMetadata.IMAGE_KEY);
		
	}

	public String getName() {
		return pr.getPropertyMetaData().getName();
	}

	public Object getPattern() {
		return mode == MODE_PATTERN;
	}

	public IProperty getProperty() {
		return pr;
	}

	public IRealm<?> getRealm() {
		DefaultPropertyMetadata propertyMetaData = (DefaultPropertyMetadata) this
				.getProperty().getPropertyMetaData();
		IRealmProvider adapter = propertyMetaData
				.getAdapter(IRealmProvider.class);
		if (adapter != null) {
			final IRealm realm = adapter.getRealm(null);
			return realm;
		}
		return null;
	}

	
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((pr == null) ? 0 : pr.hashCode());
		return result;
	}

	public boolean hasValue(Object a) {
		return values.contains(a);
	}

	public String id() {
		return pr.getId();
	}

	public boolean isEmpty() {
		return values.isEmpty();
	}

	public boolean isStale() {
		return isEmpty()||(mode==MODE_RANGE&&leftBound==null&&rightBound==null);
	}

	private Object parse(String string, Object m1) {
		Class<?> subjectClass = pr.getSubjectClass();
		if (subjectClass==Integer.class){
			try{
			m1=Integer.parseInt(string);
			}catch (NumberFormatException e) {
			}
		}
		if (subjectClass==Long.class){
			try{
			m1=Long.parseLong(string);
			}catch (NumberFormatException e) {
			}
		}
		if (subjectClass==Double.class){
			try{
			m1=Long.parseLong(string);
			}catch (NumberFormatException e) {
			}
		}
		if (subjectClass==Date.class){
			try{
			m1=new Date(Long.parseLong(string));
			}catch (NumberFormatException e) {
			}
		}
		return m1;
	}

	private void parseRange(Object o) {
		String m=o.toString().substring(RANGE_ENCODED.length());
		String[] split = m.split(",");
		String string = split[0];
		if (!string.equals("*")){
			Object m1=null;
			m1 = parse(string, m1);
			leftBound=(Comparable) m1;
		}
		else{
			leftBound=null;
		}
		if (!split[1].equals("*")){
			Object m1=null;
			m1 = parse(split[1], m1);
			rightBound=(Comparable) m1;
		}else{
			rightBound=null;
		}
		
		this.mode=MODE_RANGE;
	}

	public void readSettings(DataInputStream stream) {
		try {
			int readInt = stream.readInt();
			for (int a = 0; a < readInt; a++) {
				values.add(stream.readUTF());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		resetMatcher();
	}

	public void removeFilterListener(
			ISimpleChangeListener<? extends IFilter> listener) {
		listeners.remove(listener);
	}

	@SuppressWarnings("unchecked")
	public void removeValue(Object data) {
		boolean remove = values.remove(data);
		if (remove) {
			resetMatcher();
			for (ISimpleChangeListener l : listeners) {
				l.changed((IFilter) this, data);
			}
		}
	}

	protected void resetMatcher(){
		for (Object o:values){
			if (o.toString().startsWith(BasicValueFilter.RANGE_ENCODED)){
				parseRange(o);
				return;
			}
		}
		this.mode=MODE_VALUES;
		Class<?> subjectClass = pr.getSubjectClass();
		if (subjectClass==Integer.class){
			LinkedHashSet<Object>newValues=new LinkedHashSet<Object>();
			for (Object v:values){
				if (v instanceof String){
					try{
					newValues.add(Integer.parseInt(v.toString()));
					}catch(NumberFormatException e){
						
					}					
				}
				if (v instanceof Integer){
					newValues.add(v);
				}
			}
			values=newValues;
			return;
		}
		if (subjectClass==String.class){
			return;
		}
		if (subjectClass==Date.class){
			LinkedHashSet<Object>newValues=new LinkedHashSet<Object>();
			for (Object v:values){
				if (v instanceof String){
					try{
					newValues.add(new Date(Long.parseLong(v.toString())));
					}catch(NumberFormatException e){						
					}					
				}
				if (v instanceof Date){
					newValues.add(v);
				}
			}
			values=newValues;
			return;
		}
		IRealmProvider adapter = pr.getPropertyMetaData().getAdapter(IRealmProvider.class);
		if(adapter!=null){
		IRealm realm = adapter.getRealm(null);
		LinkedHashSet<Object>newValues=new LinkedHashSet<Object>();
		for (Object o:realm){
			if (o==null){
				o="<none>";
			}
			String string = o.toString();
			if(values.contains(string)){
				newValues.add(o);
			}
			if (values.contains(o)){
				newValues.add(o);
			}
		}
		values=newValues;
		}
	}

	public void setPattern(Object pattern) {
		mode = MODE_PATTERN;
	}

	public void setValues(Collection<Object> allowed) {
		allowed.remove(null);
		if (values.size() == allowed.size()) {
			if (values.containsAll(allowed)) {
				return;
			}
		}
		values.clear();
		values.addAll(allowed);
		resetMatcher();
		for (ISimpleChangeListener l : listeners) {
			l.changed((IFilter) this, values);
		}

	}
	
	public Set<Object>getValues(){
		return new LinkedHashSet<Object>(values);
	}

	public void storeSettings(DataOutputStream stream) {
		try {
			stream.writeInt(values.size());
			for (Object o : values) {
				if (o instanceof Date){
					Date z=(Date) o;
					stream.writeLong(z.getTime());
					continue;
				}
				stream.writeUTF(o.toString());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public String toString() {
		return getName();
	}

	/**
	 * @return the inverse
	 */
	public boolean isInverse()
	{
		return inverse;
	}

	/**
	 * @param inverse the inverse to set
	 */
	public void setInverse(boolean inverse)
	{
		this.inverse = inverse;
		for (ISimpleChangeListener l : listeners) {
			l.changed((IFilter) this, null);
		}
	}

}
